package cn.shanguoyu.base.springboot.starter.idempotent.core.strategy.impl.spel;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import cn.shanguoyu.base.springboot.starter.convention.exception.ClientException;
import cn.shanguoyu.base.springboot.starter.idempotent.aop.Idempotent;
import cn.shanguoyu.base.springboot.starter.idempotent.core.IdempotentParamWrapper;
import cn.shanguoyu.base.springboot.starter.idempotent.core.strategy.IdempotentContext;
import cn.shanguoyu.base.springboot.starter.idempotent.core.strategy.IdempotentExecuteHandler;
import cn.shanguoyu.base.springboot.starter.idempotent.core.strategy.abs.AbstractIdempotentTemplate;
import cn.shanguoyu.base.springboot.starter.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

/**
 * @description:基于 SpEL 方法验证请求幂等性，适用于 RestAPI 场景
 * @author：sgy
 * @date: 2023-06-04
 */
@RequiredArgsConstructor
public class IdempotentSpELByRestAPIExecuteHandler extends AbstractIdempotentTemplate implements IdempotentExecuteHandler {
    private final RedissonClient redissonClient;
    private final static String LOCK = "lock:spEL:restAPI";

    @Override
    protected void buildWrapper(IdempotentParamWrapper idempotentParamWrapper) {
        Idempotent idempotent = idempotentParamWrapper.getIdempotent();
        ProceedingJoinPoint joinPoint = idempotentParamWrapper.getJoinPoint();
        String lockKey = (String) SpELUtil.parseKey(idempotent.key(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
        if (StrUtil.isNotBlank(idempotentParamWrapper.getIdempotent().uniqueKeyPrefix())) {
            lockKey = StrUtil.join(idempotentParamWrapper.getIdempotent().uniqueKeyPrefix(), StrPool.COLON, lockKey);
        }
        idempotentParamWrapper.setLockKey(lockKey);
    }

    @Override
    protected void handler(IdempotentParamWrapper wrapper) {
        String lockKey = wrapper.getLockKey();
        RLock lock = redissonClient.getLock(lockKey);
        if (!lock.tryLock()) {
            throw new ClientException(wrapper.getIdempotent().message());
        }
        IdempotentContext.put(LOCK, lock);
    }

    @Override
    public void postProcessing() {
        RLock lock = null;
        try {
            lock = (RLock) IdempotentContext.getKey(LOCK);
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }
}
